﻿// //   Copyright 2007-2011 Comdiv (F. Sadykov) - http://code.google.com/u/fagim.sadykov/
// //   Supported by Media Technology LTD 
// //    
// //   Licensed under the Apache License, Version 2.0 (the "License");
// //   you may not use this file except in compliance with the License.
// //   You may obtain a copy of the License at
// //    
// //        http://www.apache.org/licenses/LICENSE-2.0
// //    
// //   Unless required by applicable law or agreed to in writing, software
// //   distributed under the License is distributed on an "AS IS" BASIS,
// //   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// //   See the License for the specific language governing permissions and
// //   limitations under the License.
// //   
// //   MODIFICATIONS HAVE BEEN MADE TO THIS FILE

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Security.Principal;
using System.Web;
using System.Xml.Linq;
using Comdiv.QWeb.Factory;
using Comdiv.QWeb.Security;
using Comdiv.QWeb.Serialization.Attributes;
using Comdiv.QWeb.Serialization.BxlParser;
using Comdiv.QWeb.Serialization.JsonParser;
using Comdiv.QWeb.Utils;

namespace Comdiv.QWeb {
	[Serialize]
	public class QWebContext {
		private const string Contextregex =
			@"^/((?<app>[^/]+)/)?(?<name>(?<root>[^/]+)/(?<leaf>[^\.]+))(\.(?<type>[^\.]+))?\.((quick)|(qweb))$";

		[IgnoreSerialize] [ThreadStatic] public static QWebContext Current;
		private string _applicationName;


		private string _contenttype;
		private string _language;
		private IPrincipal _logonuser;
		private string _rootDirectory;
		private int _statuscode;
		private string _command;
		private string _etag;
		private DateTime _ifModifiedSince;
		private string _ifNoneMatch;
		private DateTime _lastModified;
		private TextWriter _output;
		private IDictionary<string, string> _parameters;
		private string _type;
		private Uri _uri;
		private XElement _xdata;
		private bool _xdatachecked;

		public QWebContext() {
		}

		public QWebContext(Uri uri) {
			Uri = uri;
		}

		public QWebContext(string url) : this(new Uri(url.ToLower())) {
		}

		public QWebContext(HttpContextBase ctx) {
			SupportHeaders = true;
			NativeASPContext = ctx;
			try {
				var headers = ctx.Response.Headers;
			}
			catch (PlatformNotSupportedException) {
				SupportHeaders = false;
			}
		}

		public IPrincipal User {
			get {
				IPrincipal result =null;
				if (Registry != null) {
					result = Registry.PrincipalSource.CurrentUser;
				}
				return result ?? LogonUser;
			}
		}

		public IPrincipal LogonUser {
			get {
				return _logonuser ??
				       (_logonuser =
				        null != NativeASPContext
				        	? NativeASPContext.User
				        	: new GenericPrincipal(new GenericIdentity("local\\guest"), new[] {"DEFAULT"}));
			}
			set { _logonuser = value; }
		}

		[Notnull] public string ViewName { get; set; }

		[Notnull] public string MasterViewName { get; set; }

		[Notnull] public XElement XData {
			get {
				if (null == _xdata && !_xdatachecked) {
					if (Parameters.ContainsKey("_xdata")) {
						var x = get("_xdata", "");
						if (x.hasContent()) {
							_xdata = XElement.Parse(x);
						}
					}
					else if (Parameters.ContainsKey("_jdata")) {
						var j = get("_jdata", "");
						if (j.hasContent()) {
							_xdata = new JsonToXmlParser().Parse(j);
						}
					}
					else if (Parameters.ContainsKey("_bxdata")) {
						var bxl = get("_bxdata");
						if (bxl.hasContent()) {
							_xdata = new BxlXmlParser().NoLexData.ExtractSingle.Parse(bxl);
						}
					}

					_xdatachecked = true;
				}

				return _xdata;
			}
			set {
				_xdata = value;
				_xdatachecked = true;
			}
		}

		[Notnull] public DateTime IfModifiedSince {
			get {
				if (_ifModifiedSince == DateTime.MinValue) {
					_ifModifiedSince = new DateTime(1900, 1, 1);
					if (NativeASPContext != null) {
						var header = NativeASPContext.Request.Headers["If-Modified-Since"];
						if (header.hasContent()) {
							_ifModifiedSince =
								DateTime.ParseExact(header, "R", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal).ToLocalTime();
							_ifModifiedSince = new DateTime(IfModifiedSince.Year, IfModifiedSince.Month, IfModifiedSince.Day,
							                               IfModifiedSince.Hour, IfModifiedSince.Minute, IfModifiedSince.Second);
						}
					}
				}
				return _ifModifiedSince;
			}
			set { _ifModifiedSince = value; }
		}

		[Notnull] public string IfNoneMatch {
			get {
				if (_ifNoneMatch == null) {
					_ifNoneMatch = "";
					if (SupportHeaders) {
						var header = NativeASPContext.Request.Headers["If-None-Match"];
						if (header.hasContent()) {
							_ifNoneMatch = header;
						}
					}
				}
				return _ifNoneMatch;
			}
			set { _ifNoneMatch = value; }
		}

		[Notnull] public DateTime LastModified {
			get { return _lastModified; }
			set {
				_lastModified = value;
				if (SupportHeaders) {
					NativeASPContext.Response.Headers["Last-Modified"] = value.ToUniversalTime().ToString("R",
					                                                                                      CultureInfo.InvariantCulture);
				}
			}
		}

		[Notnull] public string Etag {
			get { return _etag; }
			set {
				_etag = value ?? "";
				if (SupportHeaders) {
					NativeASPContext.Response.Headers["Etag"] = value ?? "";
				}
			}
		}

		[Notnull] public int StatusCode {
			get {
				if (NativeASPContext == null) {
					return _statuscode;
				}
				else {
					return NativeASPContext.Response.StatusCode;
				}
			}
			set {
				if (NativeASPContext == null) {
					_statuscode = value;
				}
				else {
					NativeASPContext.Response.StatusCode = value;
				}
			}
		}

		[Notnull] public string ContentType {
			get {
				if (NativeASPContext == null) {
					return _contenttype;
				}
				else {
					return NativeASPContext.Response.ContentType;
				}
			}
			set {
				if (NativeASPContext == null) {
					_contenttype = value;
				}
				else {
					NativeASPContext.Response.ContentType = value;
				}
			}
		}

		[IgnoreSerialize] public TextWriter Output {
			get { return _output ?? (_output = null == NativeASPContext ? new StringWriter() : NativeASPContext.Response.Output); }
			set { _output = value; }
		}

		[Serialize] [Notnull] public Uri Uri {
			get {
				return _uri ??
				       (_uri =
				        null != NativeASPContext ? NativeASPContext.Request.Url : new Uri("http://localhost/_empty/_call.qweb"));
			}
			set { _uri = value; }
		}

		public IDictionary<string, string> Parameters {
			get { return _parameters ?? (_parameters = retrieveParameters()); }
		}

		[IgnoreSerialize] public HttpContextBase NativeASPContext { get; protected set; }

		[Notnull] public string ActionName {
			get { return _command ?? (_command = Uri.AbsolutePath.find(Contextregex, "name").Replace("/", ".")); }
			set { _command = value; }
		}

		[Notnull] public string RenderType {
			get {
				if (null == _type) {
					_type = Uri.AbsolutePath.find(Contextregex, "type");
					if (_type.noContent()) {
						_type = "js";
					}
				}
				return _type;
			}
			set { _type = value; }
		}

		[IgnoreSerialize] public QWebServiceRegistry Registry { get; set; }

		[Notnull] public string RootDirectory {
			get {
				if (string.IsNullOrWhiteSpace(_rootDirectory)) {
					_rootDirectory = NativeASPContext != null ? NativeASPContext.Request.PhysicalApplicationPath : Environment.CurrentDirectory;
				}
				return _rootDirectory;
			}
			set { _rootDirectory = value; }
		}


		public bool SupportHeaders { get; set; }


		[Notnull] public string ApplicationName {
			get {
				if (string.IsNullOrWhiteSpace(_applicationName)) {
					_applicationName = null != NativeASPContext ? NativeASPContext.Request.ApplicationPath : Uri.AbsolutePath.split(false, true, '/')[0];
				}
				return _applicationName;
			}
			set { _applicationName = value; }
		}

		[Notnull] public ActionDescriptor ActionDescriptor { get; set; }

		[Notnull] public AuthorizationResult AuthrizeResult { get; set; }

		[Serialize] [Notnull] public object ActionResult { get; set; }

		[Notnull] public RenderDescriptor RenderDescriptor { get; set; }

		[Notnull] public bool NotModified { get; set; }

		[Notnull] public string Language {
			get {
				if (null == _language) {
					if (null != NativeASPContext && SupportHeaders) {
						_language = NativeASPContext.Request.Headers["Accept-Language"];
					}
				}
				return _language;
			}
			set { _language = value; }
		}


		public bool IgnoreActionResult {
			get {
				if (null == RenderDescriptor) return false;
				return RenderDescriptor.IgnoreActionResult;
			}
		}


		public Exception Error { get; set; }

		public virtual XElement getx(string name) {
			var datax = get(name, "");
			if (datax.noContent()) {
				datax = "<empty></empty>";
			}
			datax = datax.Trim();
			if (datax.StartsWith("<")) {
				return XElement.Parse(datax);
			}
			else {
				return new JsonToXmlParser().Parse(datax);
			}
		}

		public void WriteOutFile(string filename) {
			if (null == NativeASPContext) throw new NotSupportedException("only for attached to native Context");
			NativeASPContext.Response.WriteFile(filename);
		}

		public object Process() {
			try {
				return ActionResult = ActionDescriptor.Process(this);
			}
			catch (Exception ex) {
				Error = ex;
				throw;
			}
		}

		public void Render() {
			RenderDescriptor.Render.Render(this);
		}

		public void RenderError(Exception ex) {
			RenderDescriptor.Render.RenderError(ex, this);
		}

		public string GetHeader(string header) {
			if (null == NativeASPContext || !SupportHeaders) return "";
			return NativeASPContext.Request.Headers[header];
		}

		public void SetHeader(string header, string value) {
			if (null == NativeASPContext || !SupportHeaders) return;
			NativeASPContext.Response.Headers[header] = value;
		}

		public string get(string name) {
			return get(name, "");
		}

		public Array getarray(Type elementtype, string name, params char[] splitters) {
			var strarray = getarray(name, splitters).Select(x => x.to(elementtype)).ToArray();
			var result = Array.CreateInstance(elementtype, strarray.Length);
			Array.Copy(strarray, result, strarray.Length);
			return result;
		}

		public string[] getarray(string name, params char[] splitters) {
			splitters = splitters.Length == 0 ? new[] {','} : splitters;
			if (Parameters.ContainsKey(name)) {
				var arraysrc = Parameters[name];
				return arraysrc.split(false, true, splitters).ToArray();
			}
			if (null != XData) {
				var a = XData.Attribute(name);
				if (null != a) {
					return a.Value.split(false, true, splitters).ToArray();
				}
				var e = XData.Elements(name);
				if (e.Count() > 1) {
					return e.Select(x => x.Value).ToArray();
				}
				if (e.Count() == 1) {
					var e_ = e.First();
					if (e_.Elements().Count() > 0) {
						return e_.Elements().Select(x => x.Value).ToArray();
					}
					else {
						return new[] {e_.Value};
					}
				}
			}
			return new string[] {};
		}

		public T get<T>(string name, T def, bool setup = false) {
			if (Parameters.ContainsKey(name) || null == XData) {
				return Parameters.get(name, defobj: def, init: setup);
			}
			var a = XData.Attribute(name);
			if (null != a) {
				if (typeof (XAttribute).IsAssignableFrom(typeof (T))) {
					return (T) (object) a;
				}
				return a.Value.to<T>();
			}
			var e = XData.Element(name);
			if (null != e) {
				if (typeof (XElement).IsAssignableFrom(typeof (T))) {
					return (T)(object)e;
				}
				return e.Value.to<T>();
			}
			return def;
		}

		private IDictionary<string, string> retrieveParameters() {
			var result = new Dictionary<string, string>();
			if (null != NativeASPContext) {
				foreach (var v in NativeASPContext.Request.Form.AllKeys) {
					if (null != v) {
						result[v.ToLower()] = NativeASPContext.Request.Form[v];
					}
				}
				foreach (var v in NativeASPContext.Request.QueryString.AllKeys) {
					if (null != v) result[v.ToLower()] = NativeASPContext.Request.QueryString[v];
				}
			}
			return result;
		}

		public QWebContext Set(string name, object value) {
			Parameters[name] = value == null ? "" : value.ToString();
			return this;
		}


		public QWebContext Setup() {
			Registry.Factory.GetAction(this);
			if (ActionDescriptor.Action is IContextualAction) {
				((IContextualAction) ActionDescriptor.Action).SetContext(this);
			}
			Registry.Factory.GetRender(this);
			AuthrizeResult = null;
			return this;
		}

		public void Release() {
			Registry.Factory.ReleaseAction(this);
			Registry.Factory.ReleaseRender(this);
		}

		public IDictionary<string, string> getdict(string paramname) {
			var prefix = paramname + ".";
			var result = new Dictionary<string, string>();
			if (null != Parameters.Keys.FirstOrDefault(x => x.StartsWith(prefix))) {
				// return by query string logic
				foreach (var parameter in Parameters) {
					if (parameter.Key.StartsWith(prefix)) {
						var key = parameter.Key.Split('.')[1];
						var val = parameter.Value;
						result[key] = val;
					}
				}
			}
			else if (null != XData) {
				if (null != XData.Attributes().FirstOrDefault(x => x.Name.LocalName.StartsWith(prefix))) {
					//attribute-named logic
					foreach (var attr in XData.Attributes()) {
						if (attr.Name.LocalName.StartsWith(prefix)) {
							var key = attr.Name.LocalName.Split('.')[1];
							var val = attr.Value;
							result[key] = val;
						}
					}
				}
				else if (null != XData.Elements().FirstOrDefault(x => x.Name.LocalName == paramname && null != x.Attribute("id"))) {
					// plain element style
					foreach (var element in XData.Elements()) {
						if (element.Name.LocalName == paramname) {
							var key = element.attr("id");
							string val;
							val = null != element.Attribute("value") ? element.attr("value") : element.Value;
							result[key] = val;
						}
					}
				}
				else if (null != XData.Elements().FirstOrDefault(x => x.Name == paramname && x.Elements().Count() > 0)) {
					//elements as name or src
					var dictsrs = XData.Element(paramname);
					foreach (var element in dictsrs.Elements()) {
						var key = element.Name.LocalName;
						if (null != element.Attribute("id")) {
							key = element.Attribute("id").Value;
						}
						var val = element.Value;
						if (null != element.Attribute("value")) {
							val = element.Attribute("value").Value;
						}

						result[key] = val;
					}
				}
			}

			return result;
		}
	}
}